package org.jflame.commons.crypto;

import org.jflame.commons.crypto.Transformation.OpMode;
import org.jflame.commons.crypto.Transformation.Padding;
import org.jflame.commons.util.StringHelper;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.interfaces.RSAKey;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;

/**
 * RSA非对称加密算法.<br>
 * 支持的分组和填充模式: RSA, ECB, NOPADDING/PKCS1PADDING/OAEPWITHMD5ANDMGF1PADDING. JDK1.7默认ECB/PKCS1PADDING<br>
 * 密钥有256/512/1024/2048/4096等不同的长度。长度越长，密码强度越大，当然计算速度也越慢
 */
public class RSAEncryptor extends AsymmetricEncryptor {

    /**
     * 构造函数,ras默认使用Transformation.RSA_ECB_PKCS1PADDING
     * 
     * @throws EncryptException
     */
    public RSAEncryptor() throws EncryptException {
        super(Transformation.RSA_ECB_PKCS1PADDING);
    }

    /**
     * 构造函数
     * 
     * @param tf Transformation
     * @throws EncryptException
     */
    public RSAEncryptor(Transformation tf) throws EncryptException {
        super(tf);
    }

    public RSAEncryptor(Transformation tf, String providerName) throws EncryptException {
        super(tf, providerName);
    }

    @Override
    public byte[] doProcess(byte[] data, CipherArgs param, int cipherMode) throws EncryptException {
        byte[] resultBytes = null;
        int mapBlockSize;
        try {
            Cipher cipher = newCipher(param, cipherMode);
            if (StringHelper.isNotEmpty(getProviderName())) {
                mapBlockSize = cipher.getBlockSize();// BouncyCastleProvider已有处理
            } else {
                // 加密数据长度 <= 模长-11, 解密数据长度 <= 模长
                mapBlockSize = ((RSAKey) param.getKey(cipherMode)).getModulus()
                        .bitLength() / 8;
                if (cipherMode == Cipher.ENCRYPT_MODE) {
                    mapBlockSize = mapBlockSize - 11;
                }
            }

            if (data.length <= mapBlockSize) {
                resultBytes = cipher.doFinal(data);
            } else {
                // 一次处理明/密文应小于密钥长度,所以需要对数据分段处理
                int offSet = 0;
                int tmpBlockSize;
                int remainLength = data.length;// 剩余长度
                ByteArrayOutputStream out = new ByteArrayOutputStream();

                while (remainLength > 0) {
                    tmpBlockSize = Math.min(remainLength, mapBlockSize);
                    out.write(cipher.doFinal(data, offSet, tmpBlockSize));

                    offSet += mapBlockSize;
                    remainLength = data.length - offSet;
                }

                resultBytes = out.toByteArray();
            }
        } catch (IllegalBlockSizeException | BadPaddingException | IOException e) {
            throw new EncryptException(e);
        }
        return resultBytes;
    }

    @Override
    public boolean isSupport() {
        return transformation.getAlg() == Alg.RSA;
    }

    public static RSAEncryptor of(OpMode opMode, Padding padding) throws EncryptException {
        return new RSAEncryptor(new Transformation(Alg.RSA, opMode, padding));
    }

}